{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "第7讲 决策树\n",
    "===\n",
    "主讲教师：高鹏\n",
    "---\n",
    "办公地点：网络空间安全学院407\n",
    "---\n",
    "联系方式：pgao@qfnu.edu.cn\n",
    "---\n",
    "面向专业：软件工程（智能数据）\n",
    "---"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 兼容python2和python3\n",
    "from __future__ import print_function\n",
    "\n",
    "import numpy as np\n",
    "import scipy as sp\n",
    "import pandas as pd\n",
    "import sklearn\n",
    "import math\n",
    "import matplotlib.pyplot as plt\n",
    "from collections import Counter\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 引言\n",
    "\n",
    "决策树（decision tree）是一种基本的分类与回归方法，本课主要讨论用于分类的决策树。决策树模型呈树形结构，在分类问题中，表示基于特征对实例进行分类的过程。它可以认为是if-then规则的集合，也可以认为是定义在特征空间与类空间上的条件概率分布。其主要优点是模型具有可读性，分类速度快，学习时，利用训练数据，根据损失函数最小化的原则建立决策树模型。预测时，对新的数据，利用决策树模型进行分类。\n",
    "\n",
    "决策树学习通常包括3个步骤∶**特征选择**、**决策树的生成**和**决策树的修剪**。这些决策树学习的思想主要来源于由Quinlan在1986年提出的ID3算法和1993年提出的C4.5算法，以及由Breiman等人在1984年提出的CART算法。本讲首先介绍决策树的基本概念，然后通过ID3和C4.5介绍特征的选择、决策树的生成以及决策树的修剪，最后介绍 CART 算法。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 决策树模型与学习\n",
    "\n",
    "## 决策树模型\n",
    "\n",
    "**定义1（决策树）**  分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点（node）和有向边（directed edge）组成。结点有两种类型∶内部结点（internal node）和叶结点（leaf node），内部结点表示一个特征或属性，叶结点表示一个类。\n",
    "\n",
    "用决策树分类，从根结点开始，对实例的某一特征进行测试，根据测试结果，将实例分配到其子结点；这时，每一个子结点对应着该特征的一个取值。如此递归地对实例进行测试并分配，直至达到叶结点。最后将实例分到叶结点的类中。\n",
    "\n",
    "下图是一个决策树的示意图.图中圆和方框分别表示内部结点和叶结点。\n",
    "\n",
    "<p align=\"center\">\n",
    "  <img width=\"200\" src=\"Lesson7-1.jpg\">\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 决策树与if-then规则\n",
    "\n",
    "可以将决策树看成一个if-then规则的集合。将决策树转换成if-then规则的过程是这样的：由决策树的根结点到叶结点的每一条路径构建一条规则；路径上内部结点的特征对应着规则的条件，而叶结点的类对应着规则的结论。决策树的路径或其对应的if-then规则集合具有一个重要的性质：互斥并且完备。这就是说，每一个实例都被一条路径或一条规则所覆盖，而且只被一条路径或一条规则所覆盖。这里所谓覆盖是指实例的特征与路径上的特征一致或实例满足规则的条件。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 决策树与条件概率分布\n",
    "\n",
    "决策树还表示给定特征条件下类的条件概率分布。这一条件概率分布定义在特征空间的一个划分（partition）上。将特征空间划分为互不相交的单元（cell）或区域（region），并在每个单元定义一个类的概率分布就构成了一个条件概率分布，决策树的一条路径对应于划分中的一个单元。决策树所表示的条件概率分布由各个单元给定条件下类的条件概率分布组成。假设$X$为表示特征的随机变量，$Y$为表示类的随机变量，那么这个条件概率分布可以表示为$P(Y|X)$。$X$取值于给定划分下单元的集合，$Y$取值于类的集合。各叶结点（单元）上的条件概率往往偏向某一个类，即属于某一类的概率较大。决策树分类时将该结点的实例强行分到条件概率大的那一类去。\n",
    "\n",
    "图（a）示意地表示了特征空间的一个划分。图中的大正方形表示特征空间。这个大正方形被若干个小矩形分割，每个小矩形表示一个单元。特征空间划分上的单元构成了一个集合，$X$取值为单元的集合。为简单起见，假设只有两类；正类和负类，即$Y$取值为$+1$和$-1$。小矩形中的数字表示单元的类。图（b）示意地表示特征空间划分确定时，特征（单元）给定条件下类的条件概率分布，图（b）中条件概率分布对应于图（a）的划分。当某个单元$c$的条件概率满足$P(Y=+1|X=c)>0.5$时，则认为这个单元属于正类，即落在这个单元的实例都被视为正例。图（c）为对应于图（b）中条件概率分布的决策树。\n",
    "\n",
    "<p align=\"center\">\n",
    "  <img width=\"700\" src=\"Lesson7-2.jpg\">\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 决策树学习\n",
    "\n",
    "假设给定训练数据集\n",
    "\n",
    "$$\n",
    "D=\\{(x_1,y_1),(x_2,y_2),\\ldots,(x_N,y_N)\\}\n",
    "$$\n",
    "\n",
    "其中，$x_i=(x_i^{(1)},x_i^{(2)},\\ldots,x_i^{(n)})^T$为输入实例（特征向量），$n$为特征个数，$y\\in\\{1,2,\\ldots,K\\}$为类标记，$i=1,2,\\ldots,N$，$N$为样本容量。学习的目标是根据给定的训练数据集构建一个决策树模型，使它能够对实例进行正确的分类。\n",
    "\n",
    "决策树学习本质上是从训练数据集中归纳出一组分类规则。与训练数据集不相矛盾的决策树（即能对训练数据进行正确分类的决策树）可能有多个，也可能一个也没有，我们需要的是一个与训练数据矛盾较小的决策树，同时具有很好的泛化能力。从另一个角度看，决策树学习是由训练数据集估计条件概率模型，基于特征空间划分的类的条件概率模型有无穷多个。我们选择的条件概率模型应该不仅对训练数据有很好的拟合，而且对未知数据有很好的预测。\n",
    "\n",
    "决策树学习用损失函数表示这一目标。如下所述，决策树学习的损失函数通常是正则化的极大似然函数。决策树学习的策略是以损失函数为目标函数的最小化。\n",
    "\n",
    "当损失函数确定以后，学习问题就变为在损失函数意义下选择最优决策树的问题。因为从所有可能的决策树中选取最优决策树是NP完全问题，所以现实中决策树学习算法通常采用启发式方法，近似求解这一最优化问题，这样得到的决策树是次优（sub-optimal）的。\n",
    "\n",
    "决策树学习的算法通常是一个递归地选择最优特征，并根据该特征对训练数据进行分割，使得对各个子数据集有一个最好的分类的过程，这一过程对应着对特征空间的划分，也对应着决策树的构建。开始，构建根结点，将所有训练数据都放在根结点，选择一个最优特征，按照这一特征将训练数据集分割成子集，使得各个子集有一个在当前条件下最好的分类。如果这些子集已经能够被基本正确分类，那么构建叶结点，并将这些子集分到所对应的叶结点中去，如果还有子集不能被基本正确分类，那么就对这些子集选择新的最优特征，继续对其进行分割，构建相应的结点。如此递归地进行下去，直至所有训练数据子集被基本正确分类，或者没有合适的特征为止。最后每个子集都被分到叶结点上，即都有了明确的类。这就生成了一棵决策树。\n",
    "\n",
    "以上方法生成的决策树可能对训练数据有很好的分类能力，但对未知的测试数据却未必有很好的分类能力，即可能发生过拟合现象，我们需要对已生成的树自下而上进行剪枝，将树变得更简单，从而使它具有更好的泛化能力，具体地，就是去掉过于细分的叶结点，使其回退到父结点，甚至更高的结点，然后将父结点或更高的结点改为新的叶结点。\n",
    "\n",
    "如果特征数量很多，也可以在决策树学习开始的时候，对特征进行选择，只留下对训练数据有足够分类能力的特征。\n",
    "\n",
    "可以看出，决策树学习算法包含特征选择、决策树的生成与决策树的剪枝过程。由于决策树表示一个条件概率分布，所以深浅不同的决策树对应着不同复杂度的概率模型，决策树的生成对应于模型的局部选择，决策树的剪枝对应于模型的全局选择。决策树的生成只考虑局部最优，相对地，决策树的剪枝则考虑全局最优。\n",
    "\n",
    "决策树学习常用的算法有ID3、C4.5与CART，下面结合这些算法分别叙述决策树学习的特征选择、决策树的生成和剪枝过程。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 特征选择\n",
    "\n",
    "## 特征选择问题\n",
    "\n",
    "特征选择在于选取对训练数据具有分类能力的特征，这样可以提高决策树学习的效率，如果利用一个特征进行分类的结果与随机分类的结果没有很大差别，则称这个特征是没有分类能力的。经验上扔掉这样的特征对决策树学习的精度影响不大。通常特征选择的准则是信息增益或信息增益比。\n",
    "\n",
    "首先通过一个例子来说明特征选择问题。\n",
    "\n",
    "### 例1\n",
    "\n",
    "<p align=\"center\">\n",
    "  <img width=\"600\" src=\"Lesson7-3.jpg\">\n",
    "</p>\n",
    "\n",
    "上表是一个由15个样本组成的贷款申请训练数据.数据包括贷款申请人的4个特征（属性）:第1个特征是年龄，有3个可能值：青年、中年、老年；第2个特征是有工作，有2个可能值：是、否；第3个特征是有自己的房子，有2个可能值：是、否；第4个特征是信贷情况，有3个可能值：非常好、好、一般。表的最后一列是类别，是否同意贷款，取2个值：是、否。\n",
    "\n",
    "希望通过所给的训练数据学习一个贷款申请的决策树，用以对未来的贷款中请进行分类，即当新的客户提出贷款申请时，根据申请人的特征利用决策树决定是否批准贷款申请。\n",
    "\n",
    "特征选择是决定用哪个特征来划分特征空间。\n",
    "\n",
    "<p align=\"center\">\n",
    "  <img width=\"300\" src=\"Lesson7-4.jpg\">\n",
    "</p>\n",
    "\n",
    "上图为从前表数据学习到的两个可能的决策树，分别由两个不同特征的根结点构成，图（a）所示的根结点的特征是年龄，有3个取值，对应于不同的取值有不同的子结点。图（b）所示的根结点的特征是有工作，有2个取值，对应于不同的取值有不同的子结点。两个决策树都可以从此延续下去。问题是：究竞选择哪个特征更好些？这就要求确定选择特征的准则，直观上，如果一个特征具有更好的分类能力，或者说，按照这一特征将训练数据集分割成子集，使得各个子集在当前条件下有最好的分类，那么就更应该选择这个特征，信息增益（information gain）就能够很好地表示这一直观的准则。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 信息增益\n",
    "\n",
    "为了便于说明，先给出熵与条件熵的定义。\n",
    "\n",
    "在信息论与概率统计中，熵（entropy）是表示随机变量不确定性的度量，设$X$是一个取有限个值的离散随机变量，其概率分布为\n",
    "\n",
    "$$\n",
    "P(X=x_i)=p_i,\\; i=1,2,\\ldots,n\n",
    "$$\n",
    "\n",
    "则随机变量$X$的熵定义为\n",
    "\n",
    "$$\n",
    "H(X)=-\\sum_{i=1}^np_i\\log p_i\n",
    "$$\n",
    "\n",
    "在式中，若$p_i=0$，则定义$0\\log 0=0$。通常，式中的对数以2为底或以e为底（自然对数），这时熵的单位分别称作比特（bit）或纳特（nat），由定义可知，熵只依赖于$X$的分布，而与$X$的取值无关，所以也可将$X$的熵记作$H(p)$，即\n",
    "\n",
    "$$\n",
    "H(p)=-\\sum_{i=1}^np_i\\log p_i\n",
    "$$\n",
    "\n",
    "熵越大，随机变量的不确定性就越大。从定义可验证\n",
    "\n",
    "$$\n",
    "0\\leq H(p)\\leq \\log n\n",
    "$$\n",
    "\n",
    "当随机变量只取两个值，例如1，0时，即$X$的分布为\n",
    "\n",
    "$$\n",
    "P(X=1)=p,\\;P(X=0)=1-p,\\;0\\leq p\\leq1\n",
    "$$\n",
    "\n",
    "熵为\n",
    "\n",
    "$$\n",
    "H(p)=-p\\log_2p-(1-p)\\log_2(1-p)\n",
    "$$\n",
    "\n",
    "这时，熵$H(p)$随概率$p$变化的曲线如图所示（单位为比特）。\n",
    "\n",
    "<p align=\"center\">\n",
    "  <img width=\"300\" src=\"Lesson7-5.jpg\">\n",
    "</p>\n",
    "\n",
    "当$p=0$或$p=1$时$H(p)=0$，随机变量完全没有不确定性。当$p=0.5$时，$H(p)=1$，熵取值最大，随机变量不确定性最大。\n",
    "\n",
    "设有随机变量$(X,Y)$，其联合概率分布为\n",
    "\n",
    "$$\n",
    "P(X=x_i,Y=y_j)=P_{ij},\\quad i=1,2,\\ldots,n;\\; j=1,2,\\ldots,m\n",
    "$$\n",
    "条件熵$H(Y|X)$表示在已知随机变量$X$的条件下随机变量$Y$的不确定性。随机变量X给定的条件下随机变量Y的条件熵（conditionalentropy）$H(Y|X)$，定义为 $X$给定条件下$Y$的条件概率分布的熵对$X$的数学期望\n",
    "\n",
    "$$\n",
    "H(Y|X)=\\sum_{i=1}^np_iH(y|X=x_i)\n",
    "$$\n",
    "\n",
    "这里，$p_i=P(X=x_i),i=1,2,\\ldots,n$\n",
    "\n",
    "当熵和条件熵中的概率由数据估计（特别是极大似然估计）得到时，所对应的熵与条件熵分别称为经验熵（empirical entropy）和经验条件熵（empirical conditional entropy）。此时，如果有0概率，令$0\\log0=0$。\n",
    "\n",
    "信息增益（information gain）表示得知特征X 的信息而使得类Y的信息的不确定性减少的程度。\n",
    "\n",
    "**定义2（信息增益）**  特征$A$对训练数据集$D$的信息增益$g(D,A)$，定义为集合$D$的经验熵$H(D)$与特征$A$给定条件下$D$的经验条件熵 $H(D|A)$之差，即\n",
    "\n",
    "$$\n",
    "g(D,A)=H(D)-H(D|A)\n",
    "$$\n",
    "\n",
    "一般地，熵$H(Y)$与条件熵$H(Y|X)$之差称为互信息（mutual information）。决策树学习中的信息增益等价于训练数据集中类与特征的互信息。\n",
    "\n",
    "决策树学习应用信息增益准则选择特征。给定训练数据集$D$和特征$A$，经验熵$H(D)$表示对数据集$D$进行分类的不确定性。而经验条件熵$H(D|A)$表示在特征$A$给定的条件下对数据集$D$进行分类的不确定性，那么它们的差，即信息增益，就表示由于特征$A$而使得对数据集$D$的分类的不确定性减少的程度。显然，对于数据集$D$而言，信息增益依赖于特征，不同的特征往往具有不同的信息增益。信息增益大的特征具有更强的分类能力。\n",
    "\n",
    "根据信息增益准则的特征选择方法是：对训练数据集（或子集）$D$，计算其每个特征的信息增益，并比较它们的大小，选择信息增益最大的特征。\n",
    "\n",
    "设训练数据集为$D$，$|D|$表示其样本容量，即样本个数。设有$K$个类$C_k,k=1,2,\\ldots,K$，$|C_k|$为属于类$C_k$的样本个数，$\\displaystyle\\sum_{k=1}^K|C_k|=|D|$。设特征$A$有$n$个不同的取值$\\{a_1,a_2,\\ldots,a_n\\}$，根据特征$A$的取值将$D$划分为$n$个子集$D_1,D_2,\\ldots,D_n$，$|D_i|$为$D_i$的样本个数，$\\sum_{i=1}^n|D_i|=|D|$。记子集$D_i$中属于类$C_k$的样本的集合为$D_{ik}$，即$D_{ik}=D_i\\cup C_k$，$D_{ik}$为$D_{ik}$的样本个数。于是信息增益的算法如下：\n",
    "\n",
    "**算法1（信息增益的算法）**\n",
    "\n",
    "输入：训练数据集$D$和特征$A$\n",
    "输出：特征$A$对训练数据集$D$的信息增益$g(D,A)$。\n",
    "\n",
    "（1）计算数据集$D$的经验熵$H(D)$\n",
    "\n",
    "$$\n",
    "H(D)=-\\sum^K_{k=1}\\frac{|C_k|}{|D|}\\log_2\\frac{|C_k|}{|D|}\n",
    "$$\n",
    "\n",
    "（2）计算特征A对数据集D的经验条件熵\n",
    "\n",
    "$$\n",
    "H(D|A)=\\sum^n_{i=1}\\frac{|D_i|}{|D|}H(D_i)=-\\sum^n_{i=1}\\frac{|D_i|}{|D|}\\sum^K_{k=1}\\frac{|D_{ik}|}{|D_i|}\\log_2\\frac{|D_{ik}|}{|D|}\n",
    "$$\n",
    "\n",
    "（3）计算信息增益\n",
    "\n",
    "$$\n",
    "g(D,A)=H(D)-H(D|A)\n",
    "$$\n",
    "\n",
    "### 例2\n",
    "\n",
    "<p align=\"center\">\n",
    "  <img width=\"600\" src=\"Lesson7-3.jpg\">\n",
    "</p>\n",
    "\n",
    "对上表所给的训练数据集$D$，根据信息增益准则选择最优特征。\n",
    "\n",
    "**解**  首先计算经验熵$H(D)$\n",
    "\n",
    "$$\n",
    "H(D)=-\\frac{9}{15}\\log_2\\frac{9}{15}-\\frac{6}{15}\\log_2\\frac{6}{15}=0.971\n",
    "$$\n",
    "\n",
    "然后计算各特征对数据集D的信息增益。分别以$A_1$，$A_2$，$A_3$，$A_4$表示年龄、有工作、有自己的房子和信贷情况4个特征，则\n",
    "\n",
    "(1)\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "g(D,A_1)=&H(D)-[\\frac{5}{15}H(D_1)+\\frac{5}{15}H(D_2)+\\frac{5}{15}H(D_3)]\\\\\n",
    "=&0.971-[\\frac{5}{15}(-\\frac{2}{5}\\log_2\\frac{2}{5}-\\frac{3}{5}\\log_2\\frac{3}{5})\\\\\n",
    "&+\\frac{5}{15}(-\\frac{3}{5}\\log_2\\frac{3}{5}-\\frac{2}{5}\\log_2\\frac{2}{5})+\\frac{5}{15}(-\\frac{4}{5}\\log_2\\frac{4}{5}-\\frac{1}{5}\\log_2\\frac{1}{5})]\\\\\n",
    "=&0.971-0.888=0.083\n",
    "\\end{align}\n",
    "$$\n",
    "\n",
    "这里$D_1$，$D_2$，$D_3$分别是$D$中$A_1$（年龄）取值为青年、中年和老年的样本子集。类似地，\n",
    "\n",
    "(2)\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "g(D,A_2)&=H(D)-[\\frac{5}{15}H(D_1)+\\frac{10}{15}H(D_2)]\\\\\n",
    "&=0.971-[\\frac{5}{15}\\times 0+\\frac{10}{15}(-\\frac{4}{10}\\log_2\\frac{4}{10}-\\frac{6}{10}\\log_2\\frac{6}{10})]=0.324\n",
    "\\end{align}\n",
    "$$\n",
    "\n",
    "(3)\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "g(D,A_2)&=0.971-[\\frac{6}{15}\\times 0+\\frac{9}{15}(-\\frac{3}{9}\\log_2\\frac{3}{9}-\\frac{6}{9}\\log_2\\frac{6}{9})]\\\\\n",
    "&=0.971-0.551=0.420\n",
    "\\end{align}\n",
    "$$\n",
    "\n",
    "(4)\n",
    "\n",
    "$$\n",
    "g(D,A_4)=0.971-0.608=0.363\n",
    "$$\n",
    "\n",
    "最后，比较各特征的信息增益值．由于特征$A_3$（有自己的房子）的信息增益值最大，所以选择特征$A_3$作为最优特征。\n",
    "\n",
    "## 信息增益比\n",
    "\n",
    "信息增益值的大小是相对于训练数据集而言的，并没有绝对意义。在分类问题困难时，也就是说在训练数据集的经验熵大的时候，信息增益值会偏大，反之，信息增益值会偏小。使用信息增益比（information gain ratio）可以对这一问题进行校正。这是特征选择的另一准则。\n",
    "\n",
    "**定义3（信息增益比）**  特征$A$对训练数据集D的信息增益比$g_R(D,A)$定义为其信息增益$g(D,A)$与训练数据集$D$的经验熵$H(D)$之比\n",
    "\n",
    "$$\n",
    "g_R(D,A)=\\frac{g(D,A)}{H(D)}\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 决策树的生成\n",
    "\n",
    "本节将介绍决策树学习的生成算法，首先介绍ID3的生成算法，然后再介绍C4.5中的生成算法。这些都是决策树学习的经典算法。\n",
    "\n",
    "## ID3算法\n",
    "\n",
    "$\\operatorname{ID3}$算法的核心是在决策树各个结点上应用信息增益准则选择特征，递归地构建决策树，具体方法是：从根结点（root node）开始，对结点计算所有可能的特征的信息增益，选择信息增益最大的特征作为结点的特征，由该特征的不同取值建立子结点；再对子结点递归地调用以上方法，构建决策树；直到所有特征的信息增益均很小或没有特征可以选择为止，最后得到一个决策树。ID3 相当于用极大似然法进行概率模型的选择。\n",
    "\n",
    "**算法2（ID3 算法）**\n",
    "\n",
    "输入：训练数据集$D$，特征集$A$，阈值$e$\n",
    "\n",
    "输出：决策树$T$\n",
    "\n",
    "（1）若$D$中所有实例属于同一类$C_k$，则$T$为单结点树，并将类$C_k$作为该结点的类标记，返回$T$\n",
    "\n",
    "（2）若$A=\\varnothing$，则$T$为单结点树，并将$D$中实例数最大的类$C_k$作为该结点的类标记，返回$T$\n",
    "\n",
    "（3）否则，按算法1计算$A$中各特征对$D$的信息增益，选择信息增益最大的特征$A_g$\n",
    "\n",
    "（4）如果$A_g$的信息增益小于阈值$\\varepsilon$，则置$T$为单结点树，并将$D$中实例数最大的类$C_k$作为该结点的类标记，返回T\n",
    "\n",
    "（5）否则，对$A_g$的每一可能值$a_i$，依$A_g=a_i$将$D$分割为若干非空子集$D_i$，将$D_i$中实例数最大的类作为标记，构建子结点，由结点及其子结点构成树$T$，返回$T$\n",
    "\n",
    "（6）对第$i$个子结点，以$D_i$为训练集，以$A-\\{A_g\\}$为特征集，递归地调用步（1）—步（5），得到子树$T_i$，返回T。\n",
    "\n",
    "### 例3\n",
    "\n",
    "<p align=\"center\">\n",
    "  <img width=\"600\" src=\"Lesson7-3.jpg\">\n",
    "</p>\n",
    "\n",
    "对上表的训练数据集，利用ID3算法建立决策树。\n",
    "\n",
    "**解**  利用例2的结果，由于特征$A_3$（有自己的房子）的信息增益值最大，所以选择特征$A_3$作为根结点的特征。它将训练数据集$D$划分为两个子集$D_1$（$A$取值为\"是\"）和$D_2$（A取值为\"否\"）。由于$D_1$只有同一类的样本点，所以它成为一个叶结点，结点的类标记为\"是\"。\n",
    "\n",
    "对$D_2$，则需从特征$A_1$（年龄），$A_2$（有工作）和$A_4$（信贷情况）中选择新的特征。计算各个特征的信息增益：\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "g(D_2,A_1)&=H(D_2)-H(D_2|A_1)=0.918-0.667=0.251\\\\\n",
    "g(D_2,A_2)&=H(D_2)-H(D_2|A_2)=0.918\\\\\n",
    "g(D_2,A_4)&=H(D_2)-H(D_2|A_4)=0.474\n",
    "\\end{align}\n",
    "$$\n",
    "\n",
    "选择信息增益最大的特征$A_2$（有工作）作为结点的特征，由于$A_2$有两个可能取值，从这一结点引出两个子结点：一个对应\"是\"（有工作）的子结点，包含3个样本，它们属于同一类，所以这是一个叶结点，类标记为\"是\"；另一个是对应\"否\"（无工作）的子结点，包含6个样本，它们也属干同一类，所以这也是一个叶结点，类标记为\"否\"。\n",
    "\n",
    "<p align=\"center\">\n",
    "  <img width=\"230\" src=\"Lesson7-6.jpg\">\n",
    "</p>\n",
    "\n",
    "这样生成一个如图所示的决策树。该决策树只用了两个特征（有两个内部结点）。\n",
    "\n",
    "ID3算法只有树的生成，所以该算法生成的树容易产生过拟合。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 定义节点类 二叉树\n",
    "class Node:\n",
    "    def __init__(self, root=True, label=None, feature_name=None, feature=None):\n",
    "        self.root = root\n",
    "        self.label = label\n",
    "        self.feature_name = feature_name\n",
    "        self.feature = feature\n",
    "        self.tree = {}\n",
    "        self.result = {\n",
    "            'label:': self.label,\n",
    "            'feature': self.feature,\n",
    "            'tree': self.tree\n",
    "        }\n",
    "\n",
    "    def __repr__(self):\n",
    "        return '{}'.format(self.result)\n",
    "\n",
    "    def add_node(self, val, node):\n",
    "        self.tree[val] = node\n",
    "\n",
    "    def predict(self, features):\n",
    "        if self.root is True:\n",
    "            return self.label\n",
    "        return self.tree[features[self.feature]].predict(features)\n",
    "\n",
    "\n",
    "class DTree:\n",
    "    def __init__(self, epsilon=0.1):\n",
    "        self.epsilon = epsilon\n",
    "        self._tree = {}\n",
    "\n",
    "    # 熵\n",
    "    @staticmethod\n",
    "    def calc_ent(datasets):\n",
    "        data_length = len(datasets)\n",
    "        label_count = {}\n",
    "        for i in range(data_length):\n",
    "            label = datasets[i][-1]\n",
    "            if label not in label_count:\n",
    "                label_count[label] = 0\n",
    "            label_count[label] += 1\n",
    "        ent = -sum([(p / data_length) * log(p / data_length, 2)\n",
    "                    for p in label_count.values()])\n",
    "        return ent\n",
    "\n",
    "    # 经验条件熵\n",
    "    def cond_ent(self, datasets, axis=0):\n",
    "        data_length = len(datasets)\n",
    "        feature_sets = {}\n",
    "        for i in range(data_length):\n",
    "            feature = datasets[i][axis]\n",
    "            if feature not in feature_sets:\n",
    "                feature_sets[feature] = []\n",
    "            feature_sets[feature].append(datasets[i])\n",
    "        cond_ent = sum([(len(p) / data_length) * self.calc_ent(p)\n",
    "                        for p in feature_sets.values()])\n",
    "        return cond_ent\n",
    "\n",
    "    # 信息增益\n",
    "    @staticmethod\n",
    "    def info_gain(ent, cond_ent):\n",
    "        return ent - cond_ent\n",
    "\n",
    "    def info_gain_train(self, datasets):\n",
    "        count = len(datasets[0]) - 1\n",
    "        ent = self.calc_ent(datasets)\n",
    "        best_feature = []\n",
    "        for c in range(count):\n",
    "            c_info_gain = self.info_gain(ent, self.cond_ent(datasets, axis=c))\n",
    "            best_feature.append((c, c_info_gain))\n",
    "        # 比较大小\n",
    "        best_ = max(best_feature, key=lambda x: x[-1])\n",
    "        return best_\n",
    "\n",
    "    def train(self, train_data):\n",
    "        \"\"\"\n",
    "        input:数据集D(DataFrame格式)，特征集A，阈值eta\n",
    "        output:决策树T\n",
    "        \"\"\"\n",
    "        _, y_train, features = train_data.iloc[:, :\n",
    "                                               -1], train_data.iloc[:,\n",
    "                                                                    -1], train_data.columns[:\n",
    "                                                                                            -1]\n",
    "        # 1,若D中实例属于同一类Ck，则T为单节点树，并将类Ck作为结点的类标记，返回T\n",
    "        if len(y_train.value_counts()) == 1:\n",
    "            return Node(root=True, label=y_train.iloc[0])\n",
    "\n",
    "        # 2, 若A为空，则T为单节点树，将D中实例树最大的类Ck作为该节点的类标记，返回T\n",
    "        if len(features) == 0:\n",
    "            return Node(\n",
    "                root=True,\n",
    "                label=y_train.value_counts().sort_values(\n",
    "                    ascending=False).index[0])\n",
    "\n",
    "        # 3,计算最大信息增益 同5.1,Ag为信息增益最大的特征\n",
    "        max_feature, max_info_gain = self.info_gain_train(np.array(train_data))\n",
    "        max_feature_name = features[max_feature]\n",
    "\n",
    "        # 4,Ag的信息增益小于阈值eta,则置T为单节点树，并将D中是实例数最大的类Ck作为该节点的类标记，返回T\n",
    "        if max_info_gain < self.epsilon:\n",
    "            return Node(\n",
    "                root=True,\n",
    "                label=y_train.value_counts().sort_values(\n",
    "                    ascending=False).index[0])\n",
    "\n",
    "        # 5,构建Ag子集\n",
    "        node_tree = Node(\n",
    "            root=False, feature_name=max_feature_name, feature=max_feature)\n",
    "\n",
    "        feature_list = train_data[max_feature_name].value_counts().index\n",
    "        for f in feature_list:\n",
    "            sub_train_df = train_data.loc[train_data[max_feature_name] ==\n",
    "                                          f].drop([max_feature_name], axis=1)\n",
    "\n",
    "            # 6, 递归生成树\n",
    "            sub_tree = self.train(sub_train_df)\n",
    "            node_tree.add_node(f, sub_tree)\n",
    "\n",
    "        # pprint.pprint(node_tree.tree)\n",
    "        return node_tree\n",
    "\n",
    "    def fit(self, train_data):\n",
    "        self._tree = self.train(train_data)\n",
    "        return self._tree\n",
    "\n",
    "    def predict(self, X_test):\n",
    "        return self._tree.predict(X_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "datasets, labels = create_data()\n",
    "data_df = pd.DataFrame(datasets, columns=labels)\n",
    "dt = DTree()\n",
    "tree = dt.fit(data_df)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "tree"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "dt.predict(['老年', '否', '否', '一般'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## C4.5的生成算法\n",
    "\n",
    "C4.5算法与ID3算法相似，C4.5算法对ID3算法进行了改进。C4.5在生成的过程中，用信息增益比来选择特征。\n",
    "\n",
    "**算法3（C4.5的生成算法）**\n",
    "\n",
    "输入：训练数据集$D$，特征集$A$，阈值$\\varepsilon$\n",
    "\n",
    "输出：决策树$T$\n",
    "\n",
    "（1）如果$D$中所有实例属于同一类$C_k$，则置$T$为单结点树，并将$C_k$作为该结点的类，返回$T$\n",
    "\n",
    "（2）如果$A=\\varnothing$，则置$T$为单结点树，并将$D$中实例数最大的类$C_k$作为该结点的类，返回T\n",
    "\n",
    "（3）否则计算$A$中各特征对$D$的信息增益比，选择信息增益比最大的特征$A_g$\n",
    "\n",
    "（4）如果$A_g$的信息增益比小于阈值$\\varepsilon$，则置$T$为单结点树，并将$D$中实例数最大的类$C_k$作为该结点的类，返回T\n",
    "\n",
    "（5）否则，对$A_g$的每一可能值$a_i$，依$A_g=a_i$将$D$分割为子集若干非空$D_i$，将$D_i$中实例数最大的类作为标记，构建子结点，由结点及其子结点构成树$T$，返回$T$\n",
    "\n",
    "（6）对结点$i$，以$D_i$为训练集，以$A-\\{A_g\\}$为特征集，递归地调用步（1）—步（5），得到子树$T_i$，返回$T_i$。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 决策树的剪枝\n",
    "\n",
    "决策树生成算法递归地产生决策树，直到不能继续下去为止，这样产生的树往往对训练数据的分类很准确，但对未知的测试数据的分类却没有那么准确，即出现过拟合现象。过拟合的原因在于学习时过多地考虑如何提高对训练数据的正确分类，从而构建出过于复杂的决策树。解决这个问题的办法是考虑决策树的复咋读，对已生成的决策树进行简化。\n",
    "\n",
    "在决策树学习中将已生成的树进行简化的过程称为剪枝（pruning）。具体地，剪枝从已生成的树上裁掉一些子树或叶结点，并将其根结点或父结点作为新的叶结点，从而简化分类树模型。\n",
    "\n",
    "本节介绍一种简单的决策树学习的剪枝算法。\n",
    "\n",
    "决策树的剪枝往往通过极小化决策树整体的损失函数（loss function）或代价函数（cost function）来实现。设树$T$的叶结点个数为$|T|$，$t$是树$T$的叶结点，该叶纳点有$N_t$个样本点，其中$k$类的样本点有$N_{tk}$个，$k=1,2,\\ldots,K$，$H_t(T)$为叶结点$t$上的经验熵，$\\alpha\\geq 0$为参数，则决策树学习的损失函数可以定义为\n",
    "\n",
    "$$\n",
    "C_\\alpha(T)=\\sum^{|T|}_{t=1}N_tH_t(T)+\\alpha|T|\n",
    "$$\n",
    "\n",
    "其中经验熵为\n",
    "\n",
    "$$\n",
    "H_t(T)=-\\sum_k\\frac{N_{tk}}{N_t}\\log\\frac{N_{tk}}{N_t}\n",
    "$$\n",
    "\n",
    "在损失函数中，\n",
    "\n",
    "$$\n",
    "C_\\alpha(T)=\\sum^{|T|}_{t=1}N_tH_t(T)=-\\sum^{|T|}_{t=1}\\sum^{K}_{k=1}N_{tk}\\log\\frac{N_{tk}}{N_t}\n",
    "$$\n",
    "\n",
    "式中$C(T)$表示模型对训练数据的预测误差，即模型与训练数据的拟合程度，$|T|$表示模型复杂度，参数$\\alpha\\geq 0$控制两者之间的影响。较大的$\\alpha$促使选择较简单的模型（树），较小的$\\alpha$促使选择较复杂的模型（树）。$\\alpha= 0$意味着只考虑模型与训练数据的拟合程度，不考虑模型的复杂度。\n",
    "\n",
    "剪枝就是当$\\alpha$确定时，选择损失函数最小的模型，即损失函数最小的子树，当$\\alpha$值确定时，子树越大，往往与训练数据的拟合越好，但是模型的复杂度就越高；相反，子树越小，模型的复杂度就越低，但是往往与训练数据的拟合不好。损失函数正好表示了对两者的平衡。\n",
    "\n",
    "可以看出，决策树生成只考虑了通过提高信息增益（或信息增益比）对训练数据进行更好的拟合。而决策树剪枝通过优化损失函数还考虑了减小模型复杂度，决策树生成学习局部的模型。而决策树剪枝学习整体的模型。\n",
    "\n",
    "上面定义的损失函数的极小化等价于正则化的极大似然估计。所以，利用损失函数最小原则进行剪枝就是用正则化的极大似然估计进行模型选择。\n",
    "\n",
    "<p align=\"center\">\n",
    "  <img width=\"400\" src=\"Lesson7-7.jpg\">\n",
    "</p>\n",
    "\n",
    "上图是决策树剪枝过程的示意图，下面介绍的剪枝算法。\n",
    "\n",
    "**算法4（树的剪枝算法）** \n",
    "\n",
    "输入：生成算法产生的整个树$T$，参数$\\alpha$\n",
    "\n",
    "输出：修剪后的子树$T_\\alpha$\n",
    "\n",
    "（1）计算每个结点的经验熵\n",
    "\n",
    "（2）递归地从树的叶结点向上回缩\n",
    "\n",
    "设一组叶结点回缩到其父结点之前与之后的整体树分别为$T_B$与$T_A$，其对应的损失函数值分别是$C_\\alpha(T_B)$与$C_\\alpha(T_A)$，如果\n",
    "\n",
    "$$\n",
    "C_\\alpha(T_A)\\leq C_\\alpha(T_B)\n",
    "$$\n",
    "\n",
    "则进行剪枝，即将父结点变为新的叶结点\n",
    "\n",
    "（3）返回（2），直至不能继续为止，得到损失函数最小的子树$T$。\n",
    "\n",
    "注意，上式只需考虑两个树的损失函数的差，其计算可以在局部进行。所以，决策树的剪枝算法可以由一种动态规划的算法实现。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# CART 算法\n",
    "\n",
    "分类与回归树（classification and regression tree，CART）模型由Breiman等人在1984年提出，是应用广泛的决策树学习方法，CART 同样由特征选择、树的生成及剪枝组成，既可以用于分类也可以用于回归。以下将用于分类与回归的树统称为决策树。\n",
    "\n",
    "CART是在给定输入随机变量$X$条件下输出随机变量$Y$的条件概率分布的学习方法，CART假设决策树是二叉树，内部结点特征的取值为\"是\"和\"否\"，左分支是取值为\"是\"的分支，右分支是取值为\"否\"的分支。这样的决策树等价于递归地二分每个特征，将输入空间即特征空间划分为有限个单元，并在这些单元上确定预测的概率分布，也就是在输入给定的条件下输出的条件概率分布。\n",
    "\n",
    "CART算法由以下两步组成：\n",
    "\n",
    "（1）决策树生成：基于训练数据集生成决策树，生成的决策树要尽量大\n",
    "\n",
    "（2）决策树剪枝：用验证数据集对已生成的树进行剪枝并选择最优子树，这时用损失函数最小作为剪枝的标准\n",
    "\n",
    "## CART生成\n",
    "\n",
    "决策树的生成就是递归地构建二叉决策树的过程。对回归树用平方误差最小化准则，对分类树用基尼指数（Gini index）最小化准则，进行特征选择，生成二叉树。\n",
    "\n",
    "### 回归树的生成\n",
    "\n",
    "假设$X$与$Y$分别为输入和输出变量，并且$Y$是连续变量，给定训练数据集$D=\\{(x_1,y_1)(x_2,y_2),\\ldots,(x_N,y_N)\\}$\n",
    "\n",
    "考虑如何生成回归树。\n",
    "\n",
    "一个回归树对应着输入空间（即特征空间）的一个划分以及在划分的单元上的输出值。假设已将输入空间划分为$M$个单元$R_1,R_2,\\ldots,R_M$，并且在每个单元$R$上有一个固定的输出值$c_m$，于是回归树模型可表示为\n",
    "\n",
    "$$\n",
    "f(x)=\\sum_{m=1}^Mc_mI(x\\in R_m)\n",
    "$$\n",
    "\n",
    "当输入空间的划分确定时，可以用平方误差$\\displaystyle\\sum_{x_i\\in R_m}(y_i-f(x_i))^2$来表示回归树对于训练数据的预测误差，用平方误差最小的准则求解每个单元上的最优输出值。易知，单元$R_m$上的$c_m$的最优值$\\hat{c}_m$是$R_m$上的所有输入实例$x_i$对应的输出$y_i$的均值，即\n",
    "\n",
    "$$\n",
    "\\hat{c}_m=\\operatorname{ave}(y_i|x_i\\in R_m)\n",
    "$$\n",
    "\n",
    "问题是怎样对输入空间进行划分。这里采用启发式的方法，选择第$j$个变量$x^{(j)}$和它取的值$s$，作为切分变量（splitting variable）和切分点（splitting point），并定义两个区域:\n",
    "\n",
    "$$\n",
    "R_1(j,s)=\\{x|x^{(j)}\\leq s\\}和R_2(j,s)=\\{x|x^{(j)}> s\\}\n",
    "$$\n",
    "\n",
    "然后寻找最优切分变量$j$和最优切分点$s$。具体地，求解\n",
    "\n",
    "$$\n",
    "\\min_{j,s}[\\min_{c_1}\\sum_{x_i\\in R_1(j,s)}(y_i-c_1)^2+\\min_{c_2}\\sum_{x_i\\in R_2(j,s)}(y_i-c_2)^2]\n",
    "$$\n",
    "\n",
    "对固定输入变量$j$可以找到最优切分点$s$\n",
    "\n",
    "$$\n",
    "\\hat{c}_1=\\operatorname{ave}(y_i|x_i\\in R_1(j,s))和\\hat{c}_2=\\operatorname{ave}(y_i|x_i\\in R_2(j,s))\n",
    "$$\n",
    "\n",
    "遍历所有输入变量，找到最优的切分变量$j$，构成一个对$(j,s)$。依此将输入空间划分为两个区域。接着，对每个区域重复上述划分过程，直到满足停止条件为止，这样就生成一棵回归树。这样的回归树通常称为最小二乘回归树（least squares regression tree），现将算法叙述如下\n",
    "\n",
    "**算法5（最小二乘回归树生成算法）**\n",
    "\n",
    "输入：训练数据集$D$\n",
    "\n",
    "输出：回归树$f(x)$\n",
    "\n",
    "在训练数据集所在的输入空间中，递归地将每个区域划分为两个子区域并决定每个子区域上的输出值，构建二叉决策树:\n",
    "\n",
    "（1）选择最优切分变量$j$与切分点$s$，求解\n",
    "\n",
    "$$\n",
    "\\min_{j,s}[\\min_{c_1}\\sum_{x_i\\in R_1(j,s)}(y_i-c_1)^2+\\min_{c_2}\\sum_{x_i\\in R_2(j,s)}(y_i-c_2)^2]\n",
    "$$\n",
    "\n",
    "遍历变量$j$，对固定的切分变量$j$扫描切分点$s$，选择使上式达到最小值的对$(j,s)$\n",
    "\n",
    "（2）用选定的对$(j,s)$划分区域并决定相应的输出值\n",
    "\n",
    "$$\n",
    "R_1(j,s)=\\{x|x^{(j)}\\leq s\\},R_2(j,s)=\\{x|x^{(j)}> s\\}\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\hat{c}_m=\\frac{1}{N_m}\\sum_{x_i\\in R_m(j,s)}y_i,\\; x\\in R_m,\\; m=1,2\n",
    "$$\n",
    "\n",
    "（3）继续对两个子区域调用步骤（1）和（2），直至满足停止条件\n",
    "\n",
    "（4）将输入空间划分为$M$个区域$R_1,R_2,\\ldots,R_M$，生成决策树\n",
    "\n",
    "$$\n",
    "f(x)=\\sum_{m=1}^M\\hat{c}_mI(x\\in R_m)\n",
    "$$\n",
    "\n",
    "### 分类树的生成\n",
    "\n",
    "分类树用基尼指数选择最优特征，同时决定该特征的最优二值切分点\n",
    "\n",
    "**定义4（基尼指数）**  分类问题中，假设有$K$个类，样本点属于第$k$类的概率为$p_k$，则概率分布的基尼指数定义为\n",
    "\n",
    "$$\n",
    "\\operatorname{Gini}(p)=\\sum_{k=1}^Kp_k(1-p_k)=1-\\sum_{k=1}^Kp^2_k\n",
    "$$\n",
    "\n",
    "对于二类分类问题，若样本点属于第1个类的概率是$p$，则概率分布的基尼指数为\n",
    "\n",
    "$$\n",
    "\\operatorname{Gini}(p)= 2p(1-p)\n",
    "$$\n",
    "\n",
    "对于给定的样本集合$D$，其基尼指数为\n",
    "\n",
    "$$\n",
    "\\operatorname{Gini}(D)=1-\\sum_{k=1}^K(\\frac{|C_k|}{|D|})^2\n",
    "$$\n",
    "\n",
    "这里，$C_k$是$D$中属于第$k$类的样本子集，$K$是类的个数。\n",
    "\n",
    "如果样本集合$D$根据特征$A$是否取某一可能值$a$被分割成$D_1$和$D_2$两部分，即\n",
    "\n",
    "$$\n",
    "D_1=\\{(x,y)\\in D|A(x)=a\\},\\; D_2=D-D_1\n",
    "$$\n",
    "\n",
    "则在特征$A$的条件下，集合$D$的基尼指数定义为\n",
    "\n",
    "$$\n",
    "\\operatorname{Gini}(D,A)=\\frac{|D_1|}{|D|}\\operatorname{Gini}(D_1)+\\frac{|D_2|}{|D|}\\operatorname{Gini}(D_2)\n",
    "$$\n",
    "\n",
    "基尼指数$\\operatorname{Gini}(D)$表示集合$D$的不确定性，基尼指数$\\operatorname{Gini}(D,A)$表示经$A=a$分割后集合$D$的不确定性。基尼指数值越大，样本集合的不确定性也就越大，这一点与熵相似。\n",
    "\n",
    "<p align=\"center\">\n",
    "  <img width=\"400\" src=\"Lesson7-8.jpg\">\n",
    "</p>\n",
    "\n",
    "上图显示二类分类问题中基尼指数$\\operatorname{Gini}(p)$、熵（单位比特）之半$\\frac{1}{2}H(p)$和分类误差率的关系。横坐标表示概率$p$，纵坐标表示损失。可以看出基尼指数和熵之半的曲线很接近，都可以近似地代表分类误差率。\n",
    "\n",
    "**算法6（CART生成算法）**\n",
    "\n",
    "输入：训练数据集$D$，停止计算的条件\n",
    "\n",
    "输出：CART决策树\n",
    "\n",
    "根据训练数据集，从根结点开始，递归地对每个结点进行以下操作，构建二叉决策树:\n",
    "\n",
    "（1）设结点的训练数据集为$D$，计算现有特征对该数据集的基尼指数。此时，对每一个特征$A$，对其可能取的每个值$a$，根据样本点对$A=a$的测试为\"是\"或\"否\"将$D$分割成$D_1$和$D_2$两部分，计算$A=a$时的基尼指数\n",
    "\n",
    "（2）在所有可能的特征$A$以及它们所有可能的切分点$a$中，选择基尼指数最小的特征及其对应的切分点作为最优特征与最优切分点。依最优特征与最优切分点，从现结点生成两个子结点，将训练数据集依特征分配到两个子结点中去\n",
    "\n",
    "（3）对两个子结点递归地调用（1）和（2），直至满足停止条件\n",
    "\n",
    "（4）生成CART决策树。\n",
    "\n",
    "算法停止计算的条件是结点中的样本个数小于预定阈值，或样本集的基尼指数小于预定阈值（样本基本属于同一类），或者没有更多特征。\n",
    "\n",
    "### 例\n",
    "\n",
    "<p align=\"center\">\n",
    "  <img width=\"600\" src=\"Lesson7-3.jpg\">\n",
    "</p>\n",
    "\n",
    "根据上表所给训练数据集，应用CART算法生成决策树\n",
    "\n",
    "**解**  首先计算各特征的基尼指数，选择最优特征以及其最优切分点。仍采用例2的记号，分别以$A_1，A_2，A_3，A_4$表示年龄、有工作、有自己的房子和信贷情况4个特征，并以1、2、3表示年龄的值为青年、中年和老年，以1、2表示有工作和有自己的房子的值为是和否，以1、2、3表示信贷情况的值为非常好、好和一般。\n",
    "\n",
    "求特征$A_1$的基尼指数\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\operatorname{Gini}(D,A_1=1)&=\\frac{5}{15}(2\\times \\frac{2}{5}\\times(1-\\frac{2}{5}))+\\frac{10}{15}(2\\times\\frac{7}{10}\\times(1-\\frac{7}{10}))=0.44\n",
    "\\operatorname{Gini}(D,A_1=2)&=0.48\n",
    "\\operatorname{Gini}(D,A_1=3)&=0.44\n",
    "\\end{align}\n",
    "$$\n",
    "\n",
    "由于$\\operatorname{Gini}(D,A_1=1)$和$\\operatorname{Gini}(D,A_1=3)$相等，且最小，所以$A_1=1$和$A_1=3$都可以选作$A_1$的最优切分点。\n",
    "\n",
    "求特征$A_2$和$A_3$的基尼指数：\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\operatorname{Gini}(D,A_2=1)&=0.32\n",
    "\\operatorname{Gini}(D,A_3=1)&=0.44\n",
    "\\end{align}\n",
    "$$\n",
    "\n",
    "由于$A_2$和$A_3$只有一个切分点，所以它们就是最优切分点。\n",
    "\n",
    "求特征$A_4$的基尼指数：\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\operatorname{Gini}(D,A_4=1)&=0.36\\\\\n",
    "\\operatorname{Gini}(D,A_4=2)&=0.47\\\\\n",
    "\\operatorname{Gini}(D,A_4=3)&=0.32\n",
    "\\end{align}\n",
    "$$\n",
    "\n",
    "$\\operatorname{Gini}(D,A_4=3)$最小，所以$A_4=3$为$A_4$的最优切分点。\n",
    "\n",
    "在$A_1A_2,A_3,A_4$几个特征中，$\\operatorname{Gini}(D,A_3=1)=0.27$最小，所以选择特征$A_3$为最优特征，$A_3=1$为其最优切分点。于是根结点生成两个子结点，一个是叶结点。对另一个结点继续使用以上方法在$A_1A_2,A_4$中选择最优特征及其最优切分点，结果是$A_2=1$．依此计算得知，所得结点都是叶结点。\n",
    "\n",
    "对于本问题，按照CART算法所生成的决策树与按照ID3算法所生成的决策树完全一致.\n",
    "\n",
    "## CART剪枝\n",
    "\n",
    "CART剪枝算法从\"完全生长\"的决策树的底端剪去一些子树，使决策树变小（模型变简单），从而能够对未知数据有更准确的预测。CART 剪枝算法由两步组成：首先从生成算法产生的决策树$T$，底端开始不断剪枝，直到$T$的根结点，形成一个子树序列${T_0,T_1,\\ldots,T_n}$；然后通过交叉验证法在独立的验证数据集上对子树序列进行测试，从中选择最优子树。\n",
    "\n",
    "（1）剪枝，形成一个子树序列\n",
    "\n",
    "在剪枝过程中，计算子树的损失函数\n",
    "\n",
    "$$\n",
    "C_\\alpha(T)=C(T)+\\alpha|T|\n",
    "$$\n",
    "\n",
    "其中，$T$为任意子树，$C(T)$为对训练数据的预测误差（如基尼指数），$|T|$为子树的叶结点个数，$\\alpha\\geq 0$为参数，$C_\\alpha(T)$为参数是$\\alpha$时的子树$T$的整体损失，参数$\\alpha$权衡训练数据的拟合程度与模型的复杂度。\n",
    "\n",
    "对固定的$\\alpha$，一定存在使损失函数$C_\\alpha(T)$最小的子树，将其表示为$T_\\alpha$。$T_\\alpha$在损失函数$C_\\alpha(T)$最小的意义下是最优的。容易验证这样的最优子树是唯一的，当$\\alpha$大的时候，最优子树$T_\\alpha$偏小；当$\\alpha$小的时候，最优子树$T$偏大。极端情况，当$\\alpha=0$时，整体树是最优的。当$\\alpha\\rightarrow\\infty$时，根结点组成的单结点树是最优的。\n",
    "\n",
    "Breiman等人证明：可以用递归的方法对树进行剪枝.将$\\alpha$从小增大，$0=\\alpha_0<\\alpha_1<\\ldots<\\alpha_n<+\\infty$，产生一系列的区间$[\\alpha_i,\\alpha_{i+1}),i=0,1,\\ldots,n$；剪枝得到的子树序列对应着区间$\\alpha\\in[\\alpha_i,\\alpha_{i+1}),i=0,1,\\ldots,n$的最优子树序列$\\{T_0,T_1,\\ldots,T_n\\}$，序列中的子树是嵌套的。\n",
    "\n",
    "具体地，从整体树$T_0$开始剪枝。对$T_0$的任意内部结点$t$，以$t$为单结点树的损失函数是\n",
    "\n",
    "$$\n",
    "C_\\alpha(t)=C(t)+\\alpha\n",
    "$$\n",
    "\n",
    "以$t$为根结点的子树$T_t$的损失函数是\n",
    "\n",
    "$$\n",
    "C_\\alpha(T_t)=C(T_t)+\\alpha|T_t|\n",
    "$$\n",
    "\n",
    "当$\\alpha=0$及$\\alpha$充分小时，有不等式\n",
    "\n",
    "$$\n",
    "C_\\alpha(T_t)<C_\\alpha(t)\n",
    "$$\n",
    "\n",
    "当$\\alpha$增大时，在某一$\\alpha$有\n",
    "\n",
    "$$\n",
    "C_\\alpha(T_t)=C_\\alpha(t)\n",
    "$$\n",
    "\n",
    "当$\\alpha$再增大时，不等式反向。只要$\\alpha=\\frac{C(t)-C(T_t)}{|T_t|-1}$，$T_t$与$t$有相同的损失函数值，而$t$的结点少，因此$t$比$T_t$更可取，对$T_t$进行剪枝。\n",
    "\n",
    "为此，对$T_0$中每一内部结点$t$，计算\n",
    "\n",
    "$$\n",
    "g(t)=\\frac{C(t)-C(T_t)}{|T_t|-1}\n",
    "$$\n",
    "\n",
    "它表示剪枝后整体损失函数减少的程度。在$T_0$中剪去$g(t)$最小的$T$，将得到的子树作为$T_t$，同时将最小的$g(t)$设为$\\alpha$。$T_1$为区间$[\\alpha_1,\\alpha_2)$的最优子树。\n",
    "\n",
    "如此剪枝下去，直至得到根结点。在这一过程中，不断地增加$\\alpha$的值，产生新的区间。\n",
    "\n",
    "（2）在剪枝得到的子树序列$T_0,T_1,\\ldots,T_n$中通过交叉验证选取最优子树$T_\\alpha$。\n",
    "\n",
    "具体地，利用独立的验证数据集，测试子树序列$T_0,T_1,\\ldots,T_n$中各棵子树的平方误差或基尼指数。平方误差或基尼指数最小的决策树被认为是最优的决策树。在子树序列中，每棵子树$T_1,T_2,\\ldots,T_n$都对应于一个参数$\\alpha_1,\\alpha_2,\\ldots,\\alpha_n$。所以，当最优子树$T_k$确定时，对应的$\\alpha_k$也确定了，即得到最优决策树$T_\\alpha$.\n",
    "\n",
    "**算法7（CART剪枝算法）**\n",
    "\n",
    "输入：CART算法生成的决策树$T_0$\n",
    "\n",
    "输出：最优决策树$T_\\alpha$\n",
    "\n",
    "（1）设$k=0$，$T=T_0$\n",
    "\n",
    "（2）设$\\alpha=+\\infty$\n",
    "\n",
    "（3）自下而上地对各内部结点$t$计算$C(T_t)$，$|T_t|$以及\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "g(t)&=\\frac{C(t)-C(T_t)}{|T_t|-1}\\\\\n",
    "\\alpha&=\\min(\\alpha,g(t))\n",
    "\\end{align}\n",
    "$$\n",
    "\n",
    "这里，$T_t$表示以$t$为根结点的子树，$C(T_t)$是对训练数据的预测误差，$|T_t|$是$T_t$的叶结点个数。\n",
    "\n",
    "（4）自上而下地访问内部结点$t$，如果有$g(t)=\\alpha$，进行剪枝，并对叶结点$t$以多数表决法决定其类，得到树$T$\n",
    "\n",
    "（5）设$k=k+1$，$\\alpha_k=\\alpha$，$T_k=T$\n",
    "\n",
    "（6）如果T不是由根结点单独构成的树，则回到步骤（4）\n",
    "\n",
    "（7）采用交叉验证法在子树序列$T_0,T_1,\\ldots,T_n$中选取最优子树$T_\\alpha$。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 例\n",
    "\n",
    "<p align=\"center\">\n",
    "  <img width=\"600\" src=\"Lesson7-3.jpg\">\n",
    "</p>\n",
    "\n",
    "根据上表所给的训练数据集，利用信息增益比（C4.5算法）生成决策树"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def create_data():\n",
    "    datasets = [['青年', '否', '否', '一般', '否'],\n",
    "               ['青年', '否', '否', '好', '否'],\n",
    "               ['青年', '是', '否', '好', '是'],\n",
    "               ['青年', '是', '是', '一般', '是'],\n",
    "               ['青年', '否', '否', '一般', '否'],\n",
    "               ['中年', '否', '否', '一般', '否'],\n",
    "               ['中年', '否', '否', '好', '否'],\n",
    "               ['中年', '是', '是', '好', '是'],\n",
    "               ['中年', '否', '是', '非常好', '是'],\n",
    "               ['中年', '否', '是', '非常好', '是'],\n",
    "               ['老年', '否', '是', '非常好', '是'],\n",
    "               ['老年', '否', '是', '好', '是'],\n",
    "               ['老年', '是', '否', '好', '是'],\n",
    "               ['老年', '是', '否', '非常好', '是'],\n",
    "               ['老年', '否', '否', '一般', '否'],\n",
    "               ]\n",
    "    labels = [u'年龄', u'有工作', u'有自己的房子', u'信贷情况', u'类别']\n",
    "    # 返回数据集和每个维度的名称\n",
    "    return datasets, labels"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "datasets, labels = create_data()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "train_data = pd.DataFrame(datasets, columns=labels)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "train_data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 熵\n",
    "def calc_ent(datasets):\n",
    "    data_length = len(datasets)\n",
    "    label_count = {}\n",
    "    for i in range(data_length):\n",
    "        label = datasets[i][-1]\n",
    "        if label not in label_count:\n",
    "            label_count[label] = 0\n",
    "        label_count[label] += 1\n",
    "    ent = -sum([(p / data_length) * log(p / data_length, 2)\n",
    "                for p in label_count.values()])\n",
    "    return ent\n",
    "# def entropy(y):\n",
    "#     \"\"\"\n",
    "#     Entropy of a label sequence\n",
    "#     \"\"\"\n",
    "#     hist = np.bincount(y)\n",
    "#     ps = hist / np.sum(hist)\n",
    "#     return -np.sum([p * np.log2(p) for p in ps if p > 0])\n",
    "\n",
    "\n",
    "# 经验条件熵\n",
    "def cond_ent(datasets, axis=0):\n",
    "    data_length = len(datasets)\n",
    "    feature_sets = {}\n",
    "    for i in range(data_length):\n",
    "        feature = datasets[i][axis]\n",
    "        if feature not in feature_sets:\n",
    "            feature_sets[feature] = []\n",
    "        feature_sets[feature].append(datasets[i])\n",
    "    cond_ent = sum(\n",
    "        [(len(p) / data_length) * calc_ent(p) for p in feature_sets.values()])\n",
    "    return cond_ent\n",
    "\n",
    "\n",
    "# 信息增益\n",
    "def info_gain(ent, cond_ent):\n",
    "    return ent - cond_ent\n",
    "\n",
    "\n",
    "def info_gain_train(datasets):\n",
    "    count = len(datasets[0]) - 1\n",
    "    ent = calc_ent(datasets)\n",
    "#     ent = entropy(datasets)\n",
    "    best_feature = []\n",
    "    for c in range(count):\n",
    "        c_info_gain = info_gain(ent, cond_ent(datasets, axis=c))\n",
    "        best_feature.append((c, c_info_gain))\n",
    "        print('特征({}) - info_gain - {:.3f}'.format(labels[c], c_info_gain))\n",
    "    # 比较大小\n",
    "    best_ = max(best_feature, key=lambda x: x[-1])\n",
    "    return '特征({})的信息增益最大，选择为根节点特征'.format(labels[best_[0]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "info_gain_train(np.array(datasets))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 例\n",
    "\n",
    "| $x_i$ | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |  \n",
    "| - | - | - | - | - | - | - | - | - | - | - |  \n",
    "| $y_i$ | 4.50 | 4.75 | 4.91 | 5.34 | 5.80 | 7.05 | 7.90 | 8.23 | 8.70 | 9.00\n",
    "\n",
    "根据上表所给的训练数据集，试用平方误差损失准则生成一个二叉回归树\n",
    "\n",
    "**解**  决策树的生成就是递归地构建二叉决策树的过程，对回归树用平方误差最小化准则，对分类树用基尼指数（Gini index）最小化准则，进行特征选择，生成二叉树"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class LeastSqRTree:\n",
    "    def __init__(self, train_X, y, epsilon):\n",
    "        # 训练集特征值\n",
    "        self.x = train_X\n",
    "        # 类别\n",
    "        self.y = y\n",
    "        # 特征总数\n",
    "        self.feature_count = train_X.shape[1]\n",
    "        # 损失阈值\n",
    "        self.epsilon = epsilon\n",
    "        # 回归树\n",
    "        self.tree = None\n",
    "\n",
    "    def _fit(self, x, y, feature_count, epsilon):\n",
    "        # 选择最优切分点变量j与切分点s\n",
    "        (j, s, minval, c1, c2) = self._divide(x, y, feature_count)\n",
    "        # 初始化树\n",
    "        tree = {\"feature\": j, \"value\": x[s, j], \"left\": None, \"right\": None}\n",
    "        if minval < self.epsilon or len(y[np.where(x[:, j] <= x[s, j])]) <= 1:\n",
    "            tree[\"left\"] = c1\n",
    "        else:\n",
    "            tree[\"left\"] = self._fit(x[np.where(x[:, j] <= x[s, j])],\n",
    "                                     y[np.where(x[:, j] <= x[s, j])],\n",
    "                                     self.feature_count, self.epsilon)\n",
    "        if minval < self.epsilon or len(y[np.where(x[:, j] > s)]) <= 1:\n",
    "            tree[\"right\"] = c2\n",
    "        else:\n",
    "            tree[\"right\"] = self._fit(x[np.where(x[:, j] > x[s, j])],\n",
    "                                      y[np.where(x[:, j] > x[s, j])],\n",
    "                                      self.feature_count, self.epsilon)\n",
    "        return tree\n",
    "\n",
    "    def fit(self):\n",
    "        self.tree = self._fit(self.x, self.y, self.feature_count, self.epsilon)\n",
    "\n",
    "    @staticmethod\n",
    "    def _divide(x, y, feature_count):\n",
    "        # 初始化损失误差\n",
    "        cost = np.zeros((feature_count, len(x)))\n",
    "        # 公式5.21\n",
    "        for i in range(feature_count):\n",
    "            for k in range(len(x)):\n",
    "                # k行i列的特征值\n",
    "                value = x[k, i]\n",
    "                y1 = y[np.where(x[:, i] <= value)]\n",
    "                c1 = np.mean(y1)\n",
    "                y2 = y[np.where(x[:, i] > value)]\n",
    "                c2 = np.mean(y2)\n",
    "                y1[:] = y1[:] - c1\n",
    "                y2[:] = y2[:] - c2\n",
    "                cost[i, k] = np.sum(y1 * y1) + np.sum(y2 * y2)\n",
    "        # 选取最优损失误差点\n",
    "        cost_index = np.where(cost == np.min(cost))\n",
    "        # 选取第几个特征值\n",
    "        j = cost_index[0][0]\n",
    "        # 选取特征值的切分点\n",
    "        s = cost_index[1][0]\n",
    "        # 求两个区域的均值c1,c2\n",
    "        c1 = np.mean(y[np.where(x[:, j] <= x[s, j])])\n",
    "        c2 = np.mean(y[np.where(x[:, j] > x[s, j])])\n",
    "        return j, s, cost[cost_index], c1, c2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "train_X = np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]).T\n",
    "y = np.array([4.50, 4.75, 4.91, 5.34, 5.80, 7.05, 7.90, 8.23, 8.70, 9.00])\n",
    "\n",
    "model_tree = LeastSqRTree(train_X, y, .2)\n",
    "model_tree.fit()\n",
    "model_tree.tree"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "根据上面程序的输出，可得到用平方误差损失准则生成一个二叉回归树：$$f(x)=\\begin{cases}\n",
    "4.72 & x \\le 3\\\\\n",
    "5.57 & 3 < x \\le 5\\\\\n",
    "7.05 & 5 < x \\le 6\\\\\n",
    "7.9 & 6 < x \\le 7 \\\\\n",
    "8.23 & 7 < x \\le 8\\\\\n",
    "8.85 & x > 8\\\\\n",
    "\\end{cases}$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 例\n",
    "\n",
    "证明CART剪枝算法中，当$\\alpha$确定时，存在唯一的最小子树$T_{\\alpha}$使损失函数$C_{\\alpha}(T)$最小\n",
    "\n",
    "**解** \n",
    "\n",
    "**第1步：**内部节点是否剪枝只与以该节点为根节点的子树有关。  \n",
    "剪枝过程：  \n",
    "计算子树的损失函数：$$C_{\\alpha}(T)=C(T)+\\alpha$$其中，$\\displaystyle C(T) = \\sum_{t=1}^{|T|}N_t (1 - \\sum_{k=1}^K (\\frac{N_{tk}}{N_t})^2)$，$|T|$是叶结点个数，$K$是类别个数。  \n",
    "有剪枝前子树$T_0$，剪枝后子树$T_1$，满足$C_{\\alpha}(T_1) \\leqslant C_{\\alpha}(T_0)$则进行剪枝。 \n",
    "\n",
    "**第2步（反证法）：**假设当$\\alpha$确定时，存在两颗子树$T_1,T_2$都使得损失函数$C_{\\alpha}$最小。  \n",
    "第1种情况：假设被剪枝的子树在同一边，易知其中一个子树会由另一个子树剪枝而得到，故不可能存在两个最优子树，原结论得证。  \n",
    "第2种情况：假设被剪枝的子树不在同一边，易知被剪枝掉的子树都可以使损失函数$C_{\\alpha}$最小，故两颗子树都可以继续剪枝，故不可能存在两个最优子树，原结论得证。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 例\n",
    "\n",
    "证明 CART 剪枝算法中求出的子树序列$\\{T_0,T_1,\\cdots,T_n\\}$分别是区间$\\alpha \\in [\\alpha_i,\\alpha_{i+1})$的最优子树$T_{\\alpha}$，这里$i=0,1,\\cdots,n,0=\\alpha_0 < \\alpha_1 < \\cdots, \\alpha_n < +\\infty$。\n",
    "\n",
    "**解**  \n",
    "\n",
    "原结论可以表述为：将$\\alpha$从小增大，$0=\\alpha_0<\\alpha_1<\\cdots<\\alpha_n < +\\infty$，在每个区间$[\\alpha_i,\\alpha_{i+1})$中，子树$T_i$是这个区间里最优的。  \n",
    "**第1步：**易证，当$\\alpha=0$时，整棵树$T_0$是最优的，当$\\alpha \\rightarrow +\\infty$时，根结点组成的单结点树（即$T_n$）是最优的。\n",
    "\n",
    "\n",
    "**第2步：**  \n",
    "&emsp;&emsp;由于每次剪枝剪的都是某个内部结点的子结点，也就是将某个内部结点的所有子结点回退到这个内部结点里，并将这个内部结点作为叶子结点。因此在计算整体的损失函数时，这个内部结点以外的值都没变，只有这个内部结点的局部损失函数改变了，因此本来需要计算全局的损失函数，但现在只需要计算内部结点剪枝前和剪枝后的损失函数。  \n",
    "从整体树$T_0$开始剪枝，对$T_0$的任意内部结点$t$    \n",
    "剪枝前的状态：有$|T_t|$个叶子结点，预测误差是$C(T_t)$  \n",
    "剪枝后的状态：只有本身一个叶子结点，预测误差是$C(t)$\n",
    "因此剪枝前的以$t$结点为根结点的子树的损失函数是$$C_{\\alpha}(T_t) = C(T_t) + \\alpha|T_t|$$剪枝后的损失函数是$$C_{\\alpha}(t) = C(t) + \\alpha$$易得，一定存在一个$\\alpha$使得$C_{\\alpha}(T_t) = C_{\\alpha}(t)$，这个值为$$\\alpha=\\frac{C(t)-C(T_t)}{|T_t|-1}$$可知，找到$\\alpha$即找到了子结点$t$，即完成了剪枝，得到最优子树$T_1$  \n",
    "根据书中第73页，采用以下公式计算剪枝后整体损失函数减少的程度：$$g(t)=\\frac{C(t)-C(T_t)}{|T_t|-1}$$在$T_0$中剪去$g(t)$最小的$T_t$，将得到的子树作为$T_1$，同时将最小的$g(t)$设为$\\alpha_1$，$T_1$为区间$[\\alpha_1,\\alpha_2)$的最优子树。  \n",
    "依次类推，子树$T_i$是区间$[\\alpha_i,\\alpha_{i+1})$里最优的，原结论得证。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 本讲概要\n",
    "\n",
    "- 分类决策树模型是表示基于特征对实例进行分类的树形结构。决策树可以转换成一个if-then规则的集合，也可以看作是定义在特征空间划分上的类的条件概率分布。\n",
    "\n",
    "- 决策树学习旨在构建一个与训练数据拟合很好，并且复杂度小的决策树。因为从可能的决策树中直接选取最优决策树是NP完全问题。现实中采用启发式方法学习次优的决策树。决策树学习算法包括3部分：特征选择、树的生成和树的剪枝。常用的算法有ID3、C4.5和CART。\n",
    "\n",
    "- 特征选择的目的在于选取对训练数据能够分类的特征，特征选择的关键是其准则。常用的准则如下\n",
    "\n",
    "（1）样本集合$D$对特征$A$的信息增益（ID3）\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "g(D,A)&=H(D)-H(D|A)\n",
    "H(D)&=-\\sum^K_{k=1}\\frac{|C_k|}{|D|}\\log_2\\frac{|C_k|}{|D|}\n",
    "H(D|A)&=\\sum^n_{i=1}\\frac{|D_i|}{|D|}H(D_i)\n",
    "\\end{align}\n",
    "$$\n",
    "\n",
    "其中，$H(D)$是数据集$D$的熵，$H(D_i)$是数据集$D_i$的熵，$H(D|A)$是数据集$D$对特征$A$的条件熵。$D_i$是$D$中特征$A$取第$i$个值的样本子集，$C_k$是$D$中属于第$k$类的样本子集。$n$是特征$A$取值的个数，$K$是类的个数。\n",
    "\n",
    "（2）样本集合$D$对特征$A$的信息增益比（C4.5）\n",
    "\n",
    "$$\n",
    "g_R(D,A)=\\frac{g(D,A)}{H(D)}\n",
    "$$\n",
    "\n",
    "其中，$g(D,A)$是信息增益，$H(D)$是数据集$D$的熵\n",
    "\n",
    "（3）样本集合$D$的基尼指数（CART）\n",
    "\n",
    "$$\n",
    "\\operatorname{Gini}(D)=1-\\sum^K_{k=1}(\\frac{|C_k|}{|D|})^2\n",
    "$$\n",
    "\n",
    "特征$A$条件下集合$D$的基尼指数：\n",
    "\n",
    "$$\n",
    "\\operatorname{Gini}(D,A)=\\frac{|D_1|}{|D|}\\operatorname{Gini}(D_1)+\\frac{|D_2|}{|D|}\\operatorname{Gini}(D_2)\n",
    "$$\n",
    "\n",
    "- 决策树的生成。通常使用信息增益最大、信息增益比最大或基尼指数最小作为特征选择的准则，决策树的生成往往通过计算信息增益或其他指标，从根结点开始，递归地产生决策树，这相当于用信息增益或其他准则不断地选取局部最优的特征，或将训练集分割为能够基本正确分类的子集。\n",
    "\n",
    "- 决策树的剪枝。由于生成的决策树存在过拟合问题，需要对它进行剪枝，以简化学到的决策树。决策树的剪枝往往从已生成的树上剪掉一些叶结点或叶结点以上的子树，并将其父结点或根结点作为新的叶结点，从而简化生成的决策树。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
